<?php
namespace Pw\MainBundle\Classes;
class NFAParse
{
	const TAG_OPEN = '[';
	const TAG_CLOSE = ']';
	private $state = 0;
	private $currCallback = null;
	private $currToken = '';
	private $currTokenParams = array();
	private $currParamName = '';
	private $currParamValue = '';
	//Length difference $inputString vs $parsedString
	private $diff = 0;
	//String with replaced elements
	private $inputString = '';
	private $tags = array();
	private $transitions = array();
	
	public function __construct($em, $request, $router) {
		$this->addTransition('[', 0, 1, 'startSaveToken');
		$this->addTransition('[', 1, 1, 'startSaveToken');
		$this->addTransition(']', 1, 2, 'endSaveToken');
		$this->addTransition('=', 1, 3, array('selfParamName', 'startSaveParamValue'));
		$this->addTransition(' ', 1, 4, 'startSaveParamName');
		$this->addTransition('[', 2, 1, 'startSaveToken');
		$this->addTransition('[', 3, 1, 'startSaveToken');
		$this->addTransition(',', 3, 4, array('endSaveParamValue', 'startSaveParamName'));
		$this->addTransition(']', 3, 2, array('endSaveParamValue', 'endSaveToken'));
		$this->addTransition('[', 4, 1, 'startSaveToken');
		$this->addTransition(']', 4, 2, 'endSaveToken');
		$this->addTransition('=', 4, 3, array('endSaveParamName', 'startSaveParamValue'));
		
		$this->addTag(new Tag('b', '<strong>', '</strong>'));
		$this->addTag(new Tag('i', '<em>', '</em>'));
		$this->addTag(new Tag('del', '<del>', '</del>'));
		$this->addTag(new Tag('p', '<p>', '</p>'));
		$this->addTag(new Tag('ol', '<ol>', '</ol>'));
		$this->addTag(new Tag('ul', '<ul>', '</ul>'));
		$this->addTag(new Tag('li', '<li>', '</li>'));
		$this->addTag(new Tag('h1', '<h1>', '</h1>'));
		$this->addTag(new Tag('h2', '<h2>', '</h2>'));
		$this->addTag(new Tag('h3', '<h3>', '</h3>'));
		$this->addTag(new Tag('h4', '<h4>', '</h4>'));
		$this->addTag(new Tag('h5', '<h5>', '</h5>'));
		$this->addTag(new Tag('h6', '<h6>', '</h6>'));
		$this->addTag(new Tag('center', '<div class="cntr">', '</div>'));
		$this->addTag(new ColorTag());
		$this->addTag(new ImageTag());
		$this->addTag(new VideoTag());
		$this->addTag(new LinkTag());
		$baseUrl = $request->getScheme() . '://' . $request->getHttpHost();
		$this->addTag(new IconTag($baseUrl));
		$this->addTag(new HeroTag($em, $baseUrl, $router));
		$this->addTag(new TalentTag($em, $baseUrl, $router));
	}

	public function addTransition($char, $state, $goTo, $callBack = null){
		$callBacks = array();
		if (!is_null($callBack)) {
			if (is_array($callBack)) {
				$callBacks = $callBack;
			} else {
				$callBacks[] = $callBack;
			}
		}
		$this->transitions[$char . $state] = array($goTo, $callBacks);
	}
	public function addTag($tag){
		$this->tags[$tag->getName()] = $tag;
	}

	private function getTransition($char) {
		$state = $this->state;
		if (isset($this->transitions[$char . $state])) {
			return $this->transitions[$char . $state];
		}
		return array($state, null);
	}
	public function parse($text) {
		$text = htmlspecialchars($text, ENT_COMPAT, 'UTF-8');
		$this->inputString = $text;
		//var_dump($text);
		$length = mb_strlen($text);
		for($i = 0; $i < $length; $i++) {
			//echo 'S' . $this->state;
			//Read next character
			$char = mb_substr($text, $i, 1);

			//Get transition array
			$res = $this->getTransition($char);
			//Call transition callback(s)
			if (!empty($res[1])) {
				//var_dump($res[1]);
				foreach ($res[1] as $callback) {					
					if (!is_null($callback) && method_exists($this, $callback)) {
						//echo 'callback:' . $callback;				
						$this->$callback($char, $i);
					}
				}
			}
			//Call current state callback
			elseif (!is_null($this->currCallback) && method_exists($this, $this->currCallback)) {
				$currcallback = $this->currCallback;
				$this->$currcallback($char, $i);
			}
			//switch to new state
			$this->state = $res[0];
			//echo $char . '<br>';
		}
//		echo '<hr>';
//		var_dump($this->currToken);
//		var_dump($this->currTokenParams);
//		echo '<hr>';
//		var_dump($this->parsedString);
		/*echo '<hr>';
		var_dump($this->inputString);*/
		return $this->inputString;
	}
	protected function startSaveToken($c = null, $pos = null){
		$this->currToken = array('start' => '', 'end' => '', 'name' => '');
		$this->currTokenParams = array();
		$this->currToken['start'] = $pos;
		$this->currCallback = 'saveToken';
	}
	protected function saveToken($c = null, $pos = null){
		$this->currToken['name'] .= $c;
	}
	protected function endSaveToken($c = null, $pos = null){
		$this->currToken['end'] = $pos;
		$this->currCallback = null;
		$this->verifyToken();
	}
	
	protected function selfParamName(){
		$this->currParamName = '_self';
		$this->currTokenParams[$this->currParamName] = '';
	}
	protected function startSaveParamName(){
		$this->currParamName = '';
		$this->currCallback = 'saveParamName';
	}
	protected function saveParamName($c = null, $pos = null){		
		$this->currParamName .= $c;		
	}
	protected function endSaveParamName($c = null, $pos = null){
		if ($this->currParamName !== '') {
			$this->currParamName = trim($this->currParamName);
			$this->currTokenParams[$this->currParamName] = '';
		}
		$this->currCallback = null;
	}
	
	protected function startSaveParamValue(){
		$this->currParamValue = '';
		$this->currCallback = 'saveParamValue';
	}
	protected function saveParamValue($c = null, $pos = null){
		/*if (!ctype_space($c)) {
			$this->currTokenParams[$this->currParamName] .= $c;
		}*/
		$this->currParamValue .= $c;	
	}
	protected function endSaveParamValue($c = null, $pos = null){
		if ($this->currParamValue !== '') {
			$this->currParamValue = trim($this->currParamValue);
			$this->currTokenParams[$this->currParamName] = $this->currParamValue;
		}
		$this->currCallback = null;
	}

	protected function isClosingToken($tokenName){
		if ('/' == mb_substr($tokenName, 0, 1)) {
			return true;
		}
		return false;
	}
	//Return instance of Tag class if tag is allowed
	protected function isAllowedToken($tokenName){
		if ($this->isClosingToken($tokenName)) {
			$tokenName = mb_substr($tokenName, 1);
		}
		if (array_key_exists($tokenName, $this->tags)) {
			return $this->tags[$tokenName];
		}
		return false;
	}
	
	private function doReplace($str, $startPos, $endPos){
		$endPos += 1;
		$res = mb_substr($this->inputString, 0, $startPos + $this->diff);
		$res .= $str;
		$res .= mb_substr($this->inputString, $endPos + $this->diff);
		$this->inputString = $res;
		$this->diff += mb_strlen($str) - ($endPos - $startPos);
		/*echo 'diff';
		var_dump($this->diff);
		echo 'mb_strlen($str)';
		var_dump(mb_strlen($str));
		echo '$endPos';
		var_dump($endPos);
		echo '$startPos';
		var_dump($startPos);*/
	}

	protected function verifyToken(){
		if ($tag = $this->isAllowedToken($this->currToken['name'])) {
			/*echo 'currTokenParams:';
			var_dump($this->currTokenParams);*/
			if (!empty($this->currTokenParams)) {
				$tag->addParams($this->currTokenParams);
			}			
			if ($tag->isPaired() && $this->isClosingToken($this->currToken['name'])) {
				$replaceStr = $tag->getRepalceClose();
			} else {
				$replaceStr = $tag->getRepalceOpen();
			}
			/*echo 'replaceStr:';
			var_dump($replaceStr);*/
			if ($replaceStr) {
				$this->doReplace($replaceStr, $this->currToken['start'], $this->currToken['end']);
			}			
		}
	}
}
